###############################################################################
# Copyright IBM Corp. and others 2019
#
# This program and the accompanying materials are made available under
# the terms of the Eclipse Public License 2.0 which accompanies this
# distribution and is available at https://www.eclipse.org/legal/epl-2.0/
# or the Apache License, Version 2.0 which accompanies this distribution
# and is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# This Source Code may also be made available under the following Secondary
# Licenses when the conditions for such availability set forth in the
# Eclipse Public License, v. 2.0 are satisfied: GNU General Public License,
# version 2 with the GNU Classpath Exception [1] and GNU General Public
# License, version 2 with the OpenJDK Assembly Exception [2].
#
# [1] https://www.gnu.org/software/classpath/license.html
# [2] https://openjdk.org/legal/assembly-exception.html
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 OR GPL-2.0-only WITH OpenJDK-assembly-exception-1.0
#############################################################################

# CMAKE_VERSION is not defined before 2.6.3.
if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.12"))
	cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
else()
	# Beginning with version 3.12, cmake supports a version range here
	# as a declaration from this project that new policy behaviors
	# (up to the second version) are acceptable.
	cmake_minimum_required(VERSION 3.12...3.28 FATAL_ERROR)
endif()

set(OMR_EXE_LAUNCHER "@OMR_EXE_LAUNCHER@")
set(OMR_MODULES_DIR "@OMR_MODULES_DIR@")
set(DDR_SUPPORT_DIR "@OMR_MODULES_DIR@/ddr")
set(DDR_INFO_DIR "@DDR_INFO_DIR@")
set(DDR_EXCLUDES "$<TARGET_PROPERTY:@DDR_TARGET_NAME@,EXCLUDES>")
set(DDR_OVERRIDES_FILE "$<TARGET_PROPERTY:@DDR_TARGET_NAME@,OVERRIDES_FILE>")
set(DDR_BLOB "$<TARGET_PROPERTY:@DDR_TARGET_NAME@,BLOB>")
set(DDR_SUPERSET "$<TARGET_PROPERTY:@DDR_TARGET_NAME@,SUPERSET>")
set(DDR_MACRO_LIST "${DDR_INFO_DIR}/sets/@DDR_TARGET_NAME@.macros")
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${OMR_MODULES_DIR})
set(OBJECT_SEARCH_ROOT "$<TARGET_PROPERTY:@DDR_TARGET_NAME@,DDR_OBJECT_SEARCH_ROOT>")

set(DDR_TARGETS
	$<JOIN:$<TARGET_PROPERTY:@DDR_TARGET_NAME@,DDR_TARGETS>,
	>
)
set(DDR_SUBSETS
	$<JOIN:$<TARGET_PROPERTY:@DDR_TARGET_NAME@,DDR_SUBSETS>,
	>
)
set(OBJECT_EXCLUDE_REGEX "^$<JOIN:$<TARGET_PROPERTY:@DDR_TARGET_NAME@,DDR_OBJECT_EXCLUDES>,|^>")

project(@DDR_TARGET_NAME@ LANGUAGES NONE)

include("@DDR_TOOLS_EXPORT@")
include(OmrUtility)

# Given a var, make the path specified absolute,
# assuming paths are relative to the ddr set source directory.
macro(make_absolute var_name)
	if(${var_name})
		get_filename_component(${var_name} "${${var_name}}"
			ABSOLUTE
			BASE_DIR "$<TARGET_PROPERTY:@DDR_TARGET_NAME@,SOURCE_DIR>")
	endif()
endmacro()

make_absolute(DDR_BLOB)
make_absolute(DDR_EXCLUDES)
make_absolute(DDR_OVERRIDES_FILE)
make_absolute(DDR_SUPERSET)

function(get_relative_path output filename base)
	get_filename_component(temp "${filename}" ABSOLUTE BASE_DIR "${base}")
	file(RELATIVE_PATH temp "${temp}" "${base}")
	set("${output}" "${temp}" PARENT_SCOPE)
endfunction()

function(add_filename_extension output filename prefix)
	get_filename_component(extension "${filename}" EXT)
	string(LENGTH "${extension}" ext_length)
	if(ext_length EQUAL 0)
		message(SEND_ERROR "No file extension found")
	endif()

	string(REGEX REPLACE "${extension}$" "${prefix}${extension}" temp "${filename}")
	set("${output}" "${temp}" PARENT_SCOPE)
endfunction()

function(process_source_files src_files)
	set(options "")
	set(one_value "SOURCE_DIR" "TARGET" "OUTPUT_STUBS" "OUTPUT_ANNOTATED")
	set(multi_value "INCLUDE_DIRS" "DEFINES" "INCLUDE_FILES" "PREINCLUDES")

	cmake_parse_arguments("OPT" "${options}" "${one_value}" "${multi_value}" ${ARGV})

	set(stub_files)
	set(annotated_files)

	# Build up the command line to the preprocessor.
	set(BASE_ARGS)
	if("@CMAKE_C_COMPILER_ID@" STREQUAL "MSVC")
		list(APPEND BASE_ARGS "-nologo")
	endif()
	foreach(incdir IN LISTS OPT_INCLUDE_DIRS)
		list(APPEND BASE_ARGS "-I${incdir}")
	endforeach()

	foreach(def IN LISTS OPT_DEFINES)
		list(APPEND BASE_ARGS "-D${def}")
	endforeach()

	foreach(source_file IN LISTS OPT_UNPARSED_ARGUMENTS)
		get_filename_component(extension "${source_file}" EXT)
		# If source_file isn't a C/C++ file, ignore it.
		if(NOT extension MATCHES "\.[ch](pp)?$")
			continue()
		endif()
		get_filename_component(abs_file "${source_file}" ABSOLUTE BASE_DIR "${OPT_SOURCE_DIR}")
		file(RELATIVE_PATH output_file "${OPT_SOURCE_DIR}" "${abs_file}")
		string(REGEX REPLACE "\\.\\.(/|$)" "__\\1" output_file "${output_file}")
		set(output_file "${OPT_TARGET}/${output_file}")
		get_filename_component(output_dir "${output_file}" DIRECTORY)
		file(MAKE_DIRECTORY "${output_dir}")

		add_filename_extension(stub_file "${output_file}" ".stub")
		add_filename_extension(annt_file "${output_file}" ".annt")

		list(APPEND stub_files "${stub_file}")
		list(APPEND annotated_files "${annt_file}")

		add_custom_command(
			OUTPUT "${stub_file}"
			DEPENDS
				${abs_file}
				${DDR_SUPPORT_DIR}/cmake_ddr.awk
				${DDR_SUPPORT_DIR}/GenerateStub.cmake
			COMMAND ${CMAKE_COMMAND}
				-DAWK_SCRIPT=${DDR_SUPPORT_DIR}/cmake_ddr.awk
				-DUSE_PATH_TOOL=@USE_PATH_TOOL@
				-Dinput_file=${abs_file}
				-Doutput_file=${stub_file}
				-P ${DDR_SUPPORT_DIR}/GenerateStub.cmake
			COMMENT ""
			VERBATIM
		)

		set(pp_command "@CMAKE_C_COMPILER@")
		if("@CMAKE_C_COMPILER_IS_XLCLANG@")
			# XLClang seems to have trouble with *.h and *.hpp files here, claiming:
			# "error: invalid argument '-std=gnu++0x' not allowed with 'C/ObjC'"
			# Adding this option to the command alleviates this issue.
			list(APPEND pp_command "-xc++")
		endif()
		if("@OMR_OS_ZOS@" AND "@OMR_ENV_DATA64@" AND NOT "@OMR_TOOLCONFIG@" STREQUAL "openxl")
			# We need to set 64 bit code generation to ensure that limits.h macros are set correctly.
			list(APPEND pp_command "-Wc,lp64")
		endif()
		if("@OMR_OS_WINDOWS@" AND extension MATCHES "\.hpp$")
			# Visual Studio needs to be told that .hpp files are C++ code.
			list(APPEND pp_command "/TP")
		endif()
		list(APPEND pp_command ${BASE_ARGS} "-E" "${stub_file}")
		add_custom_command(
			OUTPUT "${annt_file}"
			DEPENDS "${stub_file}"
			COMMAND ${pp_command} | awk "/^DDRFILE_BEGIN /,/^DDRFILE_END / { gsub(/[\\t\\r ]*$/, \"\"); if (NF != 0) { print \"@\" $0 } }" > ${annt_file}
			COMMENT ""
			VERBATIM
		)
	endforeach()

	if(OPT_OUTPUT_STUBS)
		set("${OPT_OUTPUT_STUBS}" "${stub_files}" PARENT_SCOPE)
	endif()

	if(OPT_OUTPUT_ANNOTATED)
		set("${OPT_OUTPUT_ANNOTATED}" "${annotated_files}" PARENT_SCOPE)
	endif()
endfunction(process_source_files)

set(stubannotated_files "")
set(target_files "")

function(process_target target)
	file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${target}")
	set(target_info_file "${DDR_INFO_DIR}/targets/${target}.txt")
	# Make cmake configuration depend on input file.
	set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${target_info_file}")
	file(STRINGS "${target_info_file}" target_config)

	cmake_parse_arguments("OPT" "" "SOURCE_DIR;OUTPUT_FILE" "INCLUDE_PATH;DEFINES;SOURCES;HEADERS;PREINCLUDES" ${target_config})

	if(OPT_OUTPUT_FILE)
		set(target_files ${target_files} "${OPT_OUTPUT_FILE}" PARENT_SCOPE)
	endif()

	process_source_files(
		${OPT_SOURCES}
		TARGET "${target}"
		SOURCE_DIR "${OPT_SOURCE_DIR}"
		OUTPUT_ANNOTATED project_annt_src
		INCLUDE_DIRS ${OPT_INCLUDE_PATH}
		DEFINES ${OPT_DEFINES}
	)
	list(APPEND annotated_files ${project_annt_src})

	# The only difference between source files and header files, is that header files may need
	# other headers pre-included so that preprocessor macros work properly.
	process_source_files(
		${OPT_HEADERS}
		TARGET "${target}"
		SOURCE_DIR "${OPT_SOURCE_DIR}"
		OUTPUT_ANNOTATED project_annt_hdr
		INCLUDE_DIRS ${OPT_INCLUDE_PATH}
		DEFINES ${OPT_DEFINES}
		# TODO need to add pre-include files
	)
	list(APPEND annotated_files ${project_annt_hdr})

	# Bump changes up to parent scope.
	set(annotated_files "${annotated_files}" PARENT_SCOPE)
endfunction(process_target)

foreach(target IN LISTS DDR_TARGETS)
	process_target("${target}")
endforeach()

set(subset_macro_lists "")
foreach(subset IN LISTS DDR_SUBSETS)
	list(APPEND subset_macro_lists "${DDR_INFO_DIR}/sets/${subset}.macros")
endforeach()

add_custom_command(
	OUTPUT ${DDR_MACRO_LIST}
	DEPENDS ${annotated_files} ${subset_macro_lists}
	COMMAND cat ${annotated_files} ${subset_macro_lists} > ${DDR_MACRO_LIST}
)

set(EXTRA_DDRGEN_OPTIONS "")
if(DDR_EXCLUDES)
	list(APPEND EXTRA_DDRGEN_OPTIONS "--excludes" "${DDR_EXCLUDES}")
endif()

if(DDR_BLOB)
	list(APPEND EXTRA_DDRGEN_OPTIONS "--blob" "${DDR_BLOB}")
endif()

if(DDR_SUPERSET)
	list(APPEND EXTRA_DDRGEN_OPTIONS "--superset" "${DDR_SUPERSET}")
endif()

if(DDR_OVERRIDES_FILE)
	# Because ddr overrides files specify relative paths, we need to run ddrgen
	# in the same directory as the overrides files.
	get_filename_component(override_file_dir "${DDR_OVERRIDES_FILE}" DIRECTORY)

	list(APPEND EXTRA_DDRGEN_OPTIONS "--overrides" "${DDR_OVERRIDES_FILE}" WORKING_DIRECTORY "${override_file_dir}")
endif()

set(subset_binaries)
foreach(subset IN LISTS DDR_SUBSETS)
	# The name of the file which subset generates, listing all of the input binaries.
	set(binaries_list_file "${DDR_INFO_DIR}/sets/${subset}.binaries")
	set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${binaries_list_file}")
	file(STRINGS "${binaries_list_file}" binaries)
	list(APPEND subset_binaries ${binaries})
endforeach()

if(ZOS)
	# On z/OS, we have to glob for the individual .dbg/.dwo files.
	set(target_files)

	if("@OMR_TOOLCONFIG@" STREQUAL "openxl")
		set(debug_file_ext ".dwo")
	else()
		set(debug_file_ext ".dbg")
	endif()

	if(OBJECT_EXCLUDE_REGEX STREQUAL "^")
		# Empty exclude regex, just include everything.
		file(GLOB_RECURSE target_files "${OBJECT_SEARCH_ROOT}/*${debug_file_ext}")
	else()
		file(GLOB_RECURSE dbg_files RELATIVE "${OBJECT_SEARCH_ROOT}" "${OBJECT_SEARCH_ROOT}/*${debug_file_ext}")
		foreach(item IN LISTS dbg_files)
			if(NOT "${item}" MATCHES "${OBJECT_EXCLUDE_REGEX}")
				list(APPEND target_files "${OBJECT_SEARCH_ROOT}/${item}")
			endif()
		endforeach()
	endif()
endif()

# Now we generate our own list of binaries, so that they can be parsed if we are a subset.
omr_join("\n" binaries_list
	${target_files}
	${subset_binaries}
)
file(WRITE "${DDR_INFO_DIR}/sets/@DDR_TARGET_NAME@.binaries" "${binaries_list}")

if(DDR_BLOB OR DDR_SUPERSET)
	add_custom_command(
		OUTPUT ${DDR_BLOB} ${DDR_SUPERSET}
		DEPENDS ${DDR_MACRO_LIST} ${target_files} ${subset_binaries} omr_ddrgen
		COMMAND ${OMR_EXE_LAUNCHER} $<TARGET_FILE:omr_ddrgen>
			${target_files} ${subset_binaries}
			--show-empty
			--macrolist ${DDR_MACRO_LIST}
			${EXTRA_DDRGEN_OPTIONS}
	)
endif()

add_custom_target(@DDR_TARGET_NAME@ ALL DEPENDS ${DDR_BLOB} ${DDR_MACRO_LIST})
